﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace MalaCSharp
{
    ///-------------------------------------------------------------------------------------------------
    /// <summary> Static helper methods for storing and retrieving values to/from Delphi format. </summary>
    ///-------------------------------------------------------------------------------------------------
    internal static class StorageHelper
    {
        private const Byte vaNull = 0x00;   // 0
        private const Byte vaList = 0x01;   // 1
        private const Byte vaInt8 = 0x02;   // 2
        private const Byte vaInt16 = 0x03;  // 3
        private const Byte vaInt32 = 0x04;  // 4
        private const Byte vaString = 0x06; // 6
        private const Byte vaFalse = 0x08;  // 8
        private const Byte vaTrue = 0x09;   // 9
        private const Byte vaDate = 0x11;   // 17

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Writes the begin marker. </summary>
        /// <param name="stream"> The stream to write to. </param>
        ///-------------------------------------------------------------------------------------------------
        public static void WriteListBegin(Stream stream)
        {
            stream.WriteByte(vaList);
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Reads the begin marker. </summary>
        /// <exception cref="EndOfStreamException"> Thrown when the end of the stream was unexpectedly
        /// reached. </exception>
        /// <exception cref="Exception">            Thrown when unexpected byte is encountered. </exception>
        /// <param name="stream"> The stream to read from. </param>
        ///-------------------------------------------------------------------------------------------------
        public static void ReadListBegin(Stream stream)
        {
            int iByte = stream.ReadByte();
            if (iByte == -1)
                throw new EndOfStreamException();
            else if (iByte != vaList)
                throw new Exception(String.Format("Unexpected byte [{0}] looking for begin list marker.", iByte.ToString()));
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Writes end end marker. </summary>
        /// <param name="stream"> The stream to write to. </param>
        ///-------------------------------------------------------------------------------------------------
        public static void WriteListEnd(Stream stream)
        {
            stream.WriteByte(vaNull);
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Reads the end marker(s). </summary>
        /// <exception cref="EndOfStreamException"> Thrown when the end of the stream was unexpectedly
        /// reached. </exception>
        /// <exception cref="Exception">            Thrown when unexpected byte is encountered. </exception>
        /// <param name="stream"> The stream to read from. </param>
        ///-------------------------------------------------------------------------------------------------
        public static void ReadListEnd(Stream stream)
        {
            int iByte = stream.ReadByte();
            if (iByte == -1)
                throw new EndOfStreamException();
            else if (iByte != vaNull)
                throw new Exception(String.Format("Unexpected byte [{0}] looking for end list marker.", iByte.ToString()));
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Writes an ANSI string. </summary>
        /// <exception cref="ArgumentException"> Thrown when one or more arguments have unsupported or
        /// illegal values. </exception>
        /// <param name="stream"> The stream to write to. </param>
        /// <param name="value">  The value to write. </param>
        ///-------------------------------------------------------------------------------------------------
        public static void WriteString(Stream stream, String value)
        {
            String trimmedValue = (value == null) ? "" : value.Trim();
            if (trimmedValue.Length > 256)
                throw new ArgumentException("String cannot be longer than 256 characters.", "value");

            Byte[] bytes = new Byte[trimmedValue.Length + 2];

            bytes[0] = vaString;
            bytes[1] = (byte)trimmedValue.Length;

            for (int i = 0; i < trimmedValue.Length; i++)
                bytes[i + 2] = Convert.ToByte(trimmedValue[i]);

            stream.Write(bytes, 0, bytes.Length);
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Writes an integer. NOTE: Only writes 32-bit integers. Original version of method
        /// attempted to only write necessary bytes with proper byte indicator, but Delphi 'read'
        /// methods would choke on certain values, for reasons that couldn't be explained. </summary>
        /// <exception cref="ArgumentOutOfRangeException"> Thrown when one or more arguments are outside
        /// the required range. </exception>
        /// <exception cref="Exception">                   Thrown when an exception error condition
        /// occurs. </exception>
        /// <param name="stream"> The stream to write to. </param>
        /// <param name="value">  The value to write. </param>
        ///-------------------------------------------------------------------------------------------------
        public static void WriteInt(Stream stream, Int32 value)
        {
            if (value < 0)
                throw new ArgumentOutOfRangeException("value");       
            else if (value <= Int32.MaxValue)
            {
                stream.WriteByte(vaInt32);

                Byte[] bytes = BitConverter.GetBytes(value);
                if (bytes.Length != 4)
                    throw new Exception("Unexpected byte array length.");

                stream.Write(bytes, 0, 4);
            }
            else
                throw new ArgumentOutOfRangeException("value");
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Writes a bool. </summary>
        /// <param name="stream"> The stream to write to. </param>
        /// <param name="value">  The value to write. </param>
        ///-------------------------------------------------------------------------------------------------
        public static void WriteBool(Stream stream, bool value)
        {
            stream.WriteByte(value ? vaTrue : vaFalse);
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Writes a fake date value. </summary>
        /// <param name="stream"> The stream to write to. </param>
        /// <param name="value">  The value to write. </param>
        ///-------------------------------------------------------------------------------------------------
        public static void WriteDate(Stream stream, DateTime value)
        {
            stream.WriteByte(vaDate);

            Double dValue = value.ToOADate();
            Byte[] bytes = BitConverter.GetBytes(dValue);
            stream.Write(bytes, 0, bytes.Length);
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Reads an ANSI string. </summary>
        /// <exception cref="EndOfStreamException"> Thrown when the end of the stream was unexpectedly
        /// reached. </exception>
        /// <exception cref="Exception">            Thrown when unexpected byte is encountered. </exception>
        /// <param name="stream"> The stream to read from. </param>
        /// <returns> The next string. </returns>
        ///-------------------------------------------------------------------------------------------------
        public static String ReadString(Stream stream)
        {
            int iByte = stream.ReadByte();
            if (iByte == -1)
                throw new EndOfStreamException();
            else if (iByte != vaString)
                throw new Exception(String.Format("Unexpected byte [{0}] looking for string marker.", iByte.ToString()));
            else
            {
                int stringLength = stream.ReadByte();
                if (stringLength == -1)
                    throw new EndOfStreamException();
                else if (stringLength == 0)
                    return "";
                else
                {
                    Byte[] bytes = new Byte[stringLength];
                    stream.Read(bytes, 0, stringLength);
                    return Encoding.Default.GetString(bytes);
                }
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets a 32-bit integer. </summary>
        /// <exception cref="EndOfStreamException"> Thrown when the end of the stream was unexpectedly
        /// reached. </exception>
        /// <exception cref="Exception">            Thrown when unexpected byte is encountered. </exception>
        /// <param name="stream"> The stream to read from. </param>
        /// <returns> Integer value. </returns>
        ///-------------------------------------------------------------------------------------------------
        public static Int32 ReadInt(Stream stream)
        {
            int iByte = stream.ReadByte();
            if (iByte == -1)
                throw new EndOfStreamException();
            else if (iByte == vaInt8)
                return Convert.ToInt32(stream.ReadByte());
            else if (iByte == vaInt16)
            {
                Byte[] bytes = new Byte[2];
                stream.Read(bytes, 0, 2);
                return Convert.ToInt32(BitConverter.ToInt16(bytes, 0));
            }
            else if (iByte == vaInt32)
            {
                Byte[] bytes = new Byte[4];
                stream.Read(bytes, 0, 4);
                return BitConverter.ToInt32(bytes, 0);
            }
            else
                throw new Exception(String.Format("Unexpected byte [{0}] looking for integer marker.", iByte.ToString()));
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Reads a bool. </summary>
        /// <exception cref="EndOfStreamException"> Thrown when the end of the stream was unexpectedly
        /// reached. </exception>
        /// <exception cref="Exception">            Thrown when unexpected byte is encountered. </exception>
        /// <param name="stream"> The stream to read from. </param>
        /// <returns> Boolean value. </returns>
        ///-------------------------------------------------------------------------------------------------
        public static bool ReadBool(Stream stream)
        {
            int iByte = stream.ReadByte();
            if (iByte == -1)
                throw new EndOfStreamException();
            else if (iByte == vaTrue)
                return true;
            else if (iByte == vaFalse)
                return false;
            else
                throw new Exception(String.Format("Unexpected byte [{0}] looking for boolean value.", iByte.ToString()));
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets the next date. Not sure on format, so no date is actually returned. </summary>
        /// <exception cref="EndOfStreamException"> Thrown when the end of the stream was unexpectedly
        /// reached. </exception>
        /// <exception cref="Exception">            Thrown when unexpected byte is encountered. </exception>
        /// <param name="stream"> The stream to read from. </param>
        /// <returns> The date. </returns>
        ///-------------------------------------------------------------------------------------------------
        public static DateTime ReadDate(Stream stream)
        {
            int iByte = stream.ReadByte();
            if (iByte == -1)
                throw new EndOfStreamException();
            else if (iByte != vaDate)
                throw new Exception(String.Format("Unexpected byte [{0}] looking for date marker.", iByte.ToString()));
            else
            {
                Byte[] bytes = new Byte[8];
                stream.Read(bytes, 0, 8);
                return DateTime.FromOADate(BitConverter.ToDouble(bytes, 0));
            }
        }
    }
}
